#include "interrupt.h"
#include "stdint.h"
#include "global.h"
#include "io.h"
#include "print.h"

#define PIC_M_CTRL	0x20		//master control port 0x20
#define PIC_M_DATA	0X21		//master data port 0x21
#define PIC_S_CTRL	0xa0		//slave control port 0xa0
#define PIC_S_DATA	0xa1		//slave data port 0xa1

#define IDT_DESC_CNT	0x30		//total count of interrupt  0-47   48 

#define EFLAGS_IF	0x00000200
#define GET_EFLAGS(EFLAGS_VAR) asm volatile("pushfl;popl %0" : "=g" (EFLAGS_VAR))

/*Interrupt Descriptor*/
struct gate_desc {
	uint16_t	func_offset_low_word;
	uint16_t	selector;
	uint8_t		dcount;		//00000000B
	uint8_t		attribute;
	uint16_t	func_offset_high_word;
};

/*build Interrupt Descriptor*/
static void make_idt_desc(struct gate_desc* p_gdesc,uint8_t attr,intr_handler function);
/*IDT array*/
static struct gate_desc idt[IDT_DESC_CNT];
/*declare function pointer array*/
extern intr_handler intr_entry_table[IDT_DESC_CNT]; //The array in kernel.S
/*save exception name*/
char* intr_name[IDT_DESC_CNT];
/*declare interrupt handler func array*/
intr_handler idt_table[IDT_DESC_CNT];

/*init PIC 8259A*/
static void pic_init(void) {
	/*master*/
	outb(PIC_M_CTRL,0x11);	//ICW1: edge trigging, casade 8259, need ICW4
	outb(PIC_M_DATA,0x20);	//ICW2: interrupt number begin with 0x20(32), IR[0-7] is 0x20~0x27
	outb(PIC_M_DATA,0X04);	//ICW3: IR2 link to slave
	outb(PIC_M_DATA,0X01);	//ICW4: 8086 moder, manully EOI

	/*slave*/
	outb(PIC_S_CTRL,0x11);	//ICW1: edge trigging, casade 8259, need ICW4
	outb(PIC_S_DATA,0x28);	//ICW2: interrupt number begin with 0x28(40), IR[0-7] is 0x28~0x2F
	outb(PIC_S_DATA,0X02);	//ICW3: set slave link to master IR2
	outb(PIC_S_DATA,0x01);	//ICW4: 8086 moder, manully EOI

	/*open timer and keyboard interrupt*/
	outb(PIC_M_DATA,0xfc);
	outb(PIC_S_DATA,0xff);

	put_str("     pic_inti done\n");
}

/*build interrupt descriptor*/
static void make_idt_desc(struct gate_desc* p_gdesc,uint8_t attr,intr_handler function) {
	p_gdesc->func_offset_low_word = (uint32_t)function & 0x0000FFFF;
	p_gdesc->selector = SELECTOR_K_CODE;
	p_gdesc->dcount = 0;
	p_gdesc->attribute = attr;
	p_gdesc->func_offset_high_word =((uint32_t)function & 0xFFFF0000) >> 16;
}

/*build Interrupt Descriptor Table*/
static void idt_desc_init(void) {
	int i;
	for(i = 0;i < IDT_DESC_CNT;i++) {
		make_idt_desc(&idt[i],IDT_DESC_ATTR_DPL0,intr_entry_table[i]);
	}
	put_str("     idt_desc_init done\n");
}

/*general handler func for interrupt*/
static void general_intr_handler(uint8_t vec_nr) {
	if (vec_nr == 0x27 || vec_nr == 0x2f) {
		return;  //IRQ7 and IRQ15 will send spurious interrupt, don't need to handle
	}
	/*set cursor 0 pos*/
	set_cursor(0);
	int cursor_pos = 0;
	while(cursor_pos < 320) {   //clean 4 line
		put_char(' ');
		cursor_pos++;
	}

	set_cursor(0);
	put_str("!!!!!!!! exception message begin !!!!!!!!\n");
	set_cursor(88);  //second line char 8
	put_str(intr_name[vec_nr]);
	if(vec_nr == 14) {
		int page_fault_vaddr = 0;
		asm ("movl %%cr2, %0" : "=r" (page_fault_vaddr));  //cr2 keep the vaddr cause PageFault
		put_str("\npage fault addr is "); 
		put_int(page_fault_vaddr);
	}
	put_str("\n!!!!!!! exception message end !!!!!!!!\n");
	/*intr_off will not be scheduled*/
	while(1);
}

/*register general interrupt handle func and exception name*/
static void exception_init(void) {
	int i;
	for (i = 0;i < IDT_DESC_CNT;i++) {
		idt_table[i] = general_intr_handler;
		intr_name[i] = "unknown";
	}	
	intr_name[0] = "#DE Divide Error";
	intr_name[1] = "#DB Debug Exception";
	intr_name[2] = "NMI Interrupt";
	intr_name[3] = "#BP Breakpoint Exception";
	intr_name[4] = "#OF Overflow Exception";
	intr_name[5] = "#BR BOUND Range Exceeded Exception";
	intr_name[6] = "#UD Invalid Opcode Exception";
	intr_name[7] = "#NM Device Not Available Exception";
	intr_name[8] = "#DF Double Fault Exception";
	intr_name[9] = "Coprocessor Segment Overrun";
	intr_name[10] = "#TS Invalid TSS Exception";
	intr_name[11] = "#NP Segment Not Present";
	intr_name[12] = "#SS Stack Fault Exception";
	intr_name[13] = "#GP General Protection Exception";
	intr_name[14] = "#PF Page-Fault Exception";
	// intr_name[15] 第15项是intel保留项，未使用
	intr_name[16] = "#MF x87 FPU Floating-Point Error";
	intr_name[17] = "#AC Alignment Check Exception";
	intr_name[18] = "#MC Machine-Check Exception";
	intr_name[19] = "#XF SIMD Floating-Point Exception";	
}

enum intr_status intr_enable() {
	enum intr_status old_status;
	if(INTR_ON == intr_get_status()) {
		old_status = INTR_ON;
		return old_status;
	} else {
		old_status = INTR_OFF;
		asm volatile("sti"); 	//open interrupt IF set 1
		return old_status;
	}
}

enum intr_status intr_disable() {
	enum intr_status old_status;
	if(INTR_ON == intr_get_status()) {
		old_status = INTR_ON;
		asm volatile("cli":::"memory");	 //close interrupt IF set 0
		return old_status;
	} else {
		old_status = INTR_OFF;
		return old_status;
	}
}

enum intr_status intr_set_status(enum intr_status status) {
	return status & INTR_ON ? intr_enable() : intr_disable();
}

enum intr_status intr_get_status(void) {
	uint32_t eflags = 0;
	GET_EFLAGS(eflags);
	return (EFLAGS_IF & eflags) ? INTR_ON : INTR_OFF;
}

void register_handler(uint8_t vector_no, intr_handler function) {
	idt_table[vector_no] = function;
}

/*finish all interrupt init jobs*/
void idt_init() {
	put_str("idt_init start\n");
	idt_desc_init();
	exception_init();
	pic_init();

	/*load idt to IDTR*/
	uint64_t idt_operand = ((sizeof(idt) - 1) | ((uint64_t)(uint32_t)idt << 16));
	asm volatile("lidt %0"::"m"(idt_operand));
	put_str("idt_init done\n");
}



























